Dynamiczne repeating section w Nintex Forms – jak to zrobić
Table of Contents
W swoim ostatnim projekcie musiałem stworzyć dynamiczną listę osób zatwierdzających w procesie, wybranych na podstawie lokalizacji i kwoty oraz kilku innych parametrach, jednak to nie ma znaczenia teraz. Na początku pomyślałem naturalnie, by stworzyć listę SharePoint, która będzie przechowywać odpowiednie mapowania. Następnie pomyślałem o akcji w przepływie pracy, która po prostu odpyta tę listę i korzystając z filtrowania uzyska listę tylko tych rekordów, które faktycznie reprezentować będą zatwierdzających dla danego procesu, którym następnie proces przypisze zadania.
Ale był haczyk 🙂 Klient oczekiwał również, że formularz będzie pokazywać listę tych dynamicznie zebranych zatwierdzających i w miarę postępów w procesie, będzie zaznaczać jak każdy z nich zatwierdzał. I również z możliwością ręcznego dodania lub usunięcia osoby z tak wygenerowanej listy!
[tds_note] Potrzebujesz pomocy w modyfikacji swoich formularzy Nintex? Skontaktuj się ze mną![/tds_note]
Pomyślałem więc, by użyć kontrolkę o nazwie Repeating Section, ale nie miałem pojęcia, w jaki sposób zasilić ją danymi dynamicznie. W końcu jednak to osiągnąłem i efekt wygląda jak poniżej:
Zaś lista źródłowa, z której pochodzą dane, wygląda następująco:
Jak zrobić taką dynamiczną repeating section?
Krok po kroku. Zaczynamy!
Struktura danych
Opisywana w tym poście aplikacja używa poniższej struktury danych (listy SharePoint):
- Locations (prosta lista, tylko z polem Tytuł)
- ApprovalThresholds – a lista zbudowana z poniższych pól:
- Title
- Approver (pole typu osoba, możliwy wielokrotny wybór)
- Location (lookup do listy Locations)
- Threshold (numer)
- OrderNo (numer, w moim wypadku chodzi o to, by umieć zidentyfikować do jakiej grupy zatwierdzających należy dany użytkownik)
- WorkingList (główna lista, na której tworzony będzie formularz)
- Title
- Location (lookup do listy Locations)
- Volume (numer)
- Approvers (pole tekstowe, wielo-linijkowe. Ważne! Musi to być typ „plain text”!)
Formularz
Stworzyłem prosty formularz dla listy WorkingList. Z pewnymi zmianami oczywiście:
- Volume – pole otrzymało zmienną JavaScript o nazwie: var_Volume;
- Location pole otrzymało zmienną JavaScript o nazwie: var_Location;
- Potem usunąłem domyślne pole „Approvers” i zastąpiłem je kontrolką repeating section:
Polu dodałem klasę CSS „approvers”:
[tds_info] To jest tutaj kluczowe. Nazwa klasy jest jedynym sposobem dla skryptu, by odnaleźć tę kontrolkę w kodzie formularza i następnie dostać się do jej zawartości.[/tds_info]
Kontrolka jest zbudowana z takich pól:
- Approver name – pole otrzymało klasę CSS: approversApprover
- Approver email – pole otrzymało klasę CSS: approversApproverEmail
- Approval group – pole otrzymało klasę CSS: approversOrderNo
Dodałem także dwa pola typu checkbox, jedno posiada zmienną JavaScript o nazwie var_IsEditMode, drugie var_IsNewMode. Są używane, by przekazać do skryptu informację, w jakim trybie otwarty jest formularz. Oba mają ustawiony parametr „Default value” na „Expression”:
I w zasadzie to wszystko, jeśli chodzi o formularz. Całą magię robi kod jQuery.
Skrypt
Skrypt odpowiada za wykonanie poniższych rzeczy:
- Wiąże listenery dla zdarzeń „change” i „blur” na polach Volume i Location;
- Definiuje funkcję, która jest uruchamiana w przypadku wystąpienia zdarzeń;
- Dodaje obsługę repeating section w trybie edycji formularza;
- Pozwala także na ukrywanie (lub pozostawianie nietkniętych) kontrolek dla obsługi repeating section – usuwanie wierszy, dodawanie wierszy (zmienna hideNativeRepeatingSectionControlls)
AD. 1 – wiązanie listenerów
var clientContext = new SP.ClientContext();
var siteurl = _spPageContextInfo.webAbsoluteUrl;
var hideNativeRepeatingSectionControlls = 0;
NWF$(document).ready(function () {
//hide "add row" link in repeating section
if (hideNativeRepeatingSectionControlls) NWF$(".approvers").find('.nf-repeater-addrow').css("visibility", "hidden");
//trigger if location, CapitalExp or Volume is changed - recalculate list of Approvers
NWF$("#" + var_Location).change(function () { retrieveApprovers(); });
NWF$("#" + var_Volume).blur(function () { retrieveApprovers(); });
if (NWF$("#" + var_IsEditMode).prop("checked")) redrawRepeatingTableEditMode();
});
AD. 2- funkcja obsługująca zmiany na polach
function retrieveApprovers() {
var oList = clientContext.get_web().get_lists().getByTitle('ApprovalThresholds');
var camlQuery = new SP.CamlQuery();
var locationArr = NWF$("#" + var_Location).val().split(";#");
var location = locationArr[1];
var locationCaml = '<Eq><FieldRef Name="Location" /><Value Type="LookupMulti">' + location + '</Value></Eq>';
var volume = NWF$("#" + var_Volume).val().replace(/[\,]/gi, "");
if (!volume) volume= 0;
camlQuery.set_viewXml('<View><Query><Where><And>' + locationCaml +
'<Leq><FieldRef Name="Threshold" /><Value Type="Number">' + volume+ '</Value></Leq>' +
'</And></Where>' +
'<OrderBy><FieldRef Name="Title"/><FieldRef Name="GroupOrderNo"/></OrderBy></Query></View>');
this.collListItem = oList.getItems(camlQuery);
clientContext.load(collListItem);
clientContext.executeQueryAsync(
Function.createDelegate(this, this.onQuerySucceeded),
Function.createDelegate(this, this.onQueryFailed)
);
}
Funkcja zbiera informacje z pól Location i Volume, a następnie konstruuje z nich zapytanie CAML, które następnie wysyła do SharePoint w celu uzyskania listy zatwierdzających. W przypadku sukcesu, wykonuje funkcję „onQuerySucceeded”, która iteruje po znalezionych wierszach.
Następnie, dla każdego zatwierdzającego, w bieżącym wierszu (może być ich więcej niż jeden) woła endpoint SharePoint w celu pozyskania dodatkowych informacji o nim (email, login):
function onQuerySucceeded(sender, args) {
// Redraw the existing table, remove everything what exists, leave fresh instance
redrawRepeatingTable();
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var oListItem = listItemEnumerator.get_current();
var approvers = oListItem.get_item('Approvers');
var approvalOrder = oListItem.get_item('GroupOrderNo');
NWF$(approvers).each(function (idx, obj) {
var person = JSON.stringify(obj);
person = JSON.parse(person);
// get user's display name and ID
var approverId = person.$1T_1;
var approverName = person.$4K_1;
var userData = "";
// ask for users additional data
NWF$.ajax({
url: siteurl + "/_api/web/getuserbyid(" + approverId + ")",
method: "GET",
async: false,
headers: { "Accept": "application/json; odata=verbose" },
error: function (data) {
console.log("Error: " + data);
}
Po zebraniu komplety danych zapisuje je do pól w ostatnim, znalezionym wierszu Repeating Section (.nf-repeater-row:last) używając klasy ’.approvers’ (pamiętaj, to bardzo ważne!) jako selektor.
[tds_council] Jeśli zmienna „hideNativeRepeatingSectionControlls” funkcja usuwa także ikonkę „X” z przetwarzanego wiersza, dzięki czemu użytkownik nie będzie w stanie samodzielnie usunąć wygenerowanego wiersza.
Pamiętaj też, że nie możesz ustawić pól jako „disabled” lub ukryte korzystając z CSS lub reguł Nintex Forms. Z jakiegoś powodu elementy mające atrybut disabled lub styl „display:none” nie są brane pod uwagę podczas zapisu i wartości z nich nie trafiają do SharePoint.
By tego uniknąć używaj „visibility:hidden” (zamiast „display:none”) i pól obliczeniowych lub overlay by uczynić pole nieaktywnym.[/tds_council]
}).done(function (userData) {
NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input').val(approvalOrder);
NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input.nf-associated-control').attr("style", NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input.nf-associated-control').attr("style") + "background-color: transparent !important;");
NWF$(".approvers .nf-repeater-row:last").find('.approversApproverEmail input').val(userData.d.Email);
NWF$(".approvers .nf-repeater-row:last").find('.approversApprover input').val(approverName);
// remove image for row deletion
if (hideNativeRepeatingSectionControlls) NWF$(".approvers .nf-repeater-row:last").find('.nf-repeater-deleterow-image').css("visibility", "hidden");
// append overlay to avoid editting ;) fields must be enabled to allow proper save
if (hideNativeRepeatingSectionControlls) NWF$(".approvers .nf-repeater-row:last").append('<div class="approverOverlay"></div>');
//add next row
NWF$(".approvers").find('a').click();
});
});
}
Na koniec pętli usuwa ostatni, pusty wiersz, który zawsze pozostaje. Dodaje także, do każdego wygenerowanego wiersza, klasę „toRemoveOnReload”, dzięki czemu funkcja „redrawRepeatingTable()” będzie wiedzieć, które wiersze powinny zostać usunięte w przypadku konieczności przegenerowania zawartości kontrolki (np. z powodu zmiany wartości parametrów wejściowych).
// remove last, empty row, as it is always empty
NWF$(".approvers .nf-repeater-row:last").find('.nf-repeater-deleterow-image').click();
// mark all additional rows as to be removed once control requires redraw:
var addedRowsSuffixes = NWF$("input[name$='InternalRepeaterAddedRowSuffixes']").val().split(",");
NWF$(addedRowsSuffixes).each(function (key, val) {
NWF$("div[name='" + val + "undefined'").addClass("toRemoveOnReload");
});
return true;
}
Funkcja redrawRepeatingTable() wygląda nastepująco:
//function used to delete existing rows in repeating table leaving it as new
function redrawRepeatingTable() {
//delete all existing repeating table rows, then build them again
NWF$(".approvers .toRemoveOnReload").each(function () {
NWF$(this).find('.nf-repeater-deleterow-image').click();
});
NWF$(".approvers .nf-repeater-row:last").find('.approversOrderNo input').val("").css("background-color", "rgb(248, 248, 248) !important");
NWF$(".approvers .nf-repeater-row:last").find('.approversApprover input').val("");
NWF$(".approvers .nf-repeater-row:last").find('.approversApproverEmail input').val("");
}
Ostatnią funkcją jaka jest używana w skrypcie, jest redrawRepeatingTableEditMode(). Istnieje w celu wstrzyknięcia sufiksów i klas „toRemoveOnReload” dla wierszy wygenerowanych w przypadku zbudowania zawartości repeating section w trybie edycji formularza:
//function used to enchance table of approvers created in the edit mode:
function redrawRepeatingTableEditMode() {
var suffix = 1;
var suffixes = "";
NWF$(".approvers .nf-repeater-row").each(function () {
NWF$(this).find('.nf-repeater-deleterow-image').css("visibility", "hidden");
// inject prefix into div's ID'
if (suffix > 1) {
NWF$(this).attr("id", suffix + "_" + NWF$(this).attr("id"));
NWF$(this).attr("name", suffix + "_undefined");
suffixes += suffix + "_,";
}
//increment prefix value
suffix += 1;
});
// inject suffixes into the dedicated field
NWF$(".approvers").find('.nf-repeater-addeddrow-suffixes').val(suffixes.substr(0, suffixes.length - 1));
var addedRowsSuffixes = suffixes.split(",");
NWF$(addedRowsSuffixes).each(function (key, val) {
NWF$("div[name='" + val + "undefined'").addClass("toRemoveOnReload");
});
}
[tds_note] Zauważ, że Repeating Section trzyma suffiksy każdego, wygenerowanego wiersza w ukrytym polu o naywie „InternalRepeaterAddedRowSuffixes”. Wartość pola zbudowana jest wg wzoru: #_;#_;#_ gdzie # to kolejna cyfra naturalna, licząc od 1. Zauważ rónież, że każdy wiersz posiada nazwę, która zaczyna się właśnie od tegoż sufiksu i słowa „undefined”, toteż możliwe jest również użycie tych informacji w celu iteracji po wygenerowanych wierszach.
Co ciekawe, w trybie edycji formularza ukryte pole jest puste, a sufiksów nie ma, dlatego skrypt je wstrzykuje. [/tds_note]
Podsumowanie
Ten sposób tworzenia dynamicznych repeating section nie jest jedynie dedykowany dla zbierania informacji o zatwierdzających w procesie. Może byc użyty dla tworzenia dowolnych, dynamicznych zestawów danych. Na przykład dla pokazywania produktów dla określonej kategorii, czy części dla wybranych produktów. Ilość przypadków użycia zależy tylko od Ciebie 😉
Poniżej załączyłem wyeksportowany formularz (Nintex Forms 2016) oraz JavaScript. Powodzenia!
[wpdm_package id=’713′]








